// Plan d'étude personnalisé : génération, consultation, progression (items cochés). import { NextResponse } from "next/server"; import { z } from "zod"; import { apiError, parseBody, requireEnrollment } from "@/lib/api.ts"; import { assertSameOrigin, requireUser } from "@/lib/auth/session.ts"; import { get, run } from "@/lib/db/index.ts"; import { generatePlan } from "@/lib/learning/plan.ts"; import { normalizeCourse } from "@/lib/learning/helpers.ts"; import { logActivity } from "@/lib/usage.ts"; export async function GET(_req: Request, ctx: { params: Promise<{ course: string }> }) { try { const user = await requireUser(); const course = normalizeCourse((await ctx.params).course); requireEnrollment(user.id, course); const plan = get<{ id: number; exam_date: string; config: string; plan: string; created_at: string }>( "SELECT id, exam_date, config, plan, created_at FROM study_plans WHERE user_id = ? AND course_code = ? AND active = 1 ORDER BY id DESC LIMIT 1", user.id, course ); return NextResponse.json({ plan: plan ? { id: plan.id, examDate: plan.exam_date, config: JSON.parse(plan.config), days: JSON.parse(plan.plan), createdAt: plan.created_at } : null, }); } catch (e) { return apiError(e); } } const createSchema = z.object({ action: z.literal("create"), examDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), weekdays: z.array(z.number().int().min(0).max(6)).min(1), minutesPerSession: z.number().int().min(20).max(360), weeksScope: z.tuple([z.number().int().min(1).max(14), z.number().int().min(1).max(14)]), }); const toggleSchema = z.object({ action: z.literal("toggle"), planId: z.number().int().positive(), date: z.string(), itemIndex: z.number().int().min(0), done: z.boolean(), }); export async function POST(req: Request, ctx: { params: Promise<{ course: string }> }) { try { await assertSameOrigin(); const user = await requireUser(); const course = normalizeCourse((await ctx.params).course); requireEnrollment(user.id, course); const body = await parseBody(req, z.discriminatedUnion("action", [createSchema, toggleSchema])); if (body.action === "create") { if (new Date(body.examDate) <= new Date()) { return NextResponse.json({ error: "La date d'examen doit être dans le futur." }, { status: 400 }); } const days = generatePlan(user.id, course, { examDate: body.examDate, weekdays: body.weekdays, minutesPerSession: body.minutesPerSession, weeksScope: body.weeksScope, }); if (!days.length) return NextResponse.json({ error: "Aucun jour disponible avant l'examen avec ces choix." }, { status: 400 }); run("UPDATE study_plans SET active = 0 WHERE user_id = ? AND course_code = ?", user.id, course); const r = run( "INSERT INTO study_plans (user_id, course_code, exam_date, config, plan, active) VALUES (?, ?, ?, ?, ?, 1)", user.id, course, body.examDate, JSON.stringify({ weekdays: body.weekdays, minutesPerSession: body.minutesPerSession, weeksScope: body.weeksScope }), JSON.stringify(days) ); logActivity(user.id, "plan", course, 60); return NextResponse.json({ ok: true, planId: Number(r.lastInsertRowid), days }); } // toggle const plan = get<{ id: number; plan: string }>( "SELECT id, plan FROM study_plans WHERE id = ? AND user_id = ? AND course_code = ?", body.planId, user.id, course ); if (!plan) return NextResponse.json({ error: "Plan introuvable." }, { status: 404 }); const days = JSON.parse(plan.plan) as { date: string; items: { done?: boolean }[] }[]; const day = days.find((d) => d.date === body.date); if (!day || !day.items[body.itemIndex]) return NextResponse.json({ error: "Élément introuvable." }, { status: 404 }); day.items[body.itemIndex].done = body.done; run("UPDATE study_plans SET plan = ? WHERE id = ?", JSON.stringify(days), plan.id); return NextResponse.json({ ok: true }); } catch (e) { return apiError(e); } }